package com.eternal.data.ui;

import androidx.paging.PagedList;

import com.eternal.base.concat.TmpHum;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;

import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.FlowableSubscriber;
import io.reactivex.functions.Function;
import io.reactivex.processors.PublishProcessor;
import io.reactivex.schedulers.Schedulers;

public class DiffUtils extends PagedList.Callback {
    private PagedList<TmpHum> now;
    private FlowableEmitter<Boolean> resultEmitter;
    private int start, oldSize;
    private long startTime;
    private boolean isAll;
    private ThreadPoolExecutor executor;
    private FlowableEmitter<Event> eventEmitter;
    private Listener listener;
    private PublishProcessor<Event> processor;

    public DiffUtils() {
        ThreadFactory factory = new ThreadFactoryBuilder()
                .setNameFormat("diff-poll-%d").build();
        executor = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES,
                new LinkedBlockingDeque<Runnable>(2), factory);
        processor = PublishProcessor.create();
        processor.onBackpressureLatest().subscribeOn(Schedulers.io()).flatMap(new Function<Event, Publisher<Boolean>>() {
            @Override
            public Publisher<Boolean> apply(Event event) throws Exception {
                start = 0;
                now = event.now;
                isAll = false;
                oldSize = event.old.size();
                if(oldSize==0){
                    return Flowable.just(true);
                }
                startTime = event.old.get(0).time;
                now.addWeakCallback(null, DiffUtils.this);
                return Flowable.create(new FlowableOnSubscribe<Boolean>() {
                    @Override
                    public void subscribe(FlowableEmitter<Boolean> emitter) {
                        resultEmitter = emitter;
                        while (start < now.size()) {
                            if (getItem(start).time <= startTime) {
                                isAll = true;
                                break;
                            }
                            ++start;
                        }
                        if (isAll) {
                            if (now.size() < start + oldSize) {
                                getItem(now.size() - 1);
                            } else {
                                resultEmitter.onNext(true);
                                resultEmitter.onComplete();
                            }
                        }
                    }
                }, BackpressureStrategy.LATEST);
            }
        }).subscribe(new FlowableSubscriber<Boolean>() {
            Subscription sub;

            @Override
            public void onSubscribe(Subscription s) {
                sub = s;
                s.request(1);
            }

            @Override
            public void onNext(Boolean aBoolean) {
                if (listener != null) {
                    listener.onEnd(now, start);
                }
                sub.request(1);
            }

            @Override
            public void onError(Throwable t) {

            }

            @Override
            public void onComplete() {

            }
        });
        Flowable.create(new FlowableOnSubscribe<Event>() {
            @Override
            public void subscribe(FlowableEmitter<Event> emitter) {
                eventEmitter = emitter;
            }
        }, BackpressureStrategy.LATEST)
                .subscribeOn(Schedulers.io())
                .flatMap(new Function<Event, Publisher<Boolean>>() {
                    @Override
                    public Publisher<Boolean> apply(Event event) throws Exception {
                        start = 0;
                        now = event.now;
                        isAll = false;
                        startTime = event.old.get(0).time;
                        oldSize = event.old.size();
                        now.addWeakCallback(null, DiffUtils.this);
                        Publisher<Boolean> publisher = Flowable.create(new FlowableOnSubscribe<Boolean>() {
                            @Override
                            public void subscribe(FlowableEmitter<Boolean> emitter) {
                                resultEmitter = emitter;
                                while (start < now.size()) {
                                    if (getItem(start).time <= startTime) {
                                        isAll = true;
                                        break;
                                    }
                                    ++start;
                                }
                                if (isAll) {
                                    if (now.size() < start + oldSize) {
                                        getItem(now.size() - 1);
                                    } else {
                                        resultEmitter.onNext(true);
                                        resultEmitter.onComplete();
                                    }
                                }
                            }
                        }, BackpressureStrategy.LATEST);
                        return publisher;
                    }
                }).subscribe(new FlowableSubscriber<Boolean>() {
            Subscription sub;

            @Override
            public void onSubscribe(Subscription s) {
                sub = s;
                s.request(1);
            }

            @Override
            public void onNext(Boolean aBoolean) {
                if (listener != null) {
                    listener.onEnd(now, start);
                }
                sub.request(1);
            }

            @Override
            public void onError(Throwable t) {

            }

            @Override
            public void onComplete() {

            }
        });
    }

    public void init(List<TmpHum> old, PagedList<TmpHum> now) {
        eventEmitter.onNext(new Event(old, now));
        processor.onNext(new Event(old, now));
    }

    public void setListener(Listener listener) {
        this.listener = listener;
    }

    private TmpHum getItem(int position) {
        now.loadAround(position);
        return now.get(position);
    }

    private void getFirst() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                while (start < now.size()) {
                    if (getItem(start).time <= startTime) {
                        isAll = true;
                        break;
                    }
                    ++start;
                }
                if (isAll) {
                    if (now.size() < start + oldSize) {
                        getItem(now.size() - 1);
                    } else {
                        resultEmitter.onNext(true);
                        resultEmitter.onComplete();
                    }
                }
            }
        });
    }

    private void getAll() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                if (now.size() < start + oldSize) {
                    getItem(now.size() - 1);
                    return;
                }
                now.removeWeakCallback(DiffUtils.this);
                resultEmitter.onNext(true);
                resultEmitter.onComplete();
            }
        });
    }

    @Override
    public void onChanged(int position, int count) {

    }

    @Override
    public void onInserted(int position, int count) {
        if (isAll) {
            getAll();
        } else {
            getFirst();
        }
    }

    @Override
    public void onRemoved(int position, int count) {

    }

    private static class Event {
        public final List<TmpHum> old;
        public final PagedList<TmpHum> now;

        public Event(List<TmpHum> old, PagedList<TmpHum> now) {
            this.old = old;
            this.now = now;
        }
    }

    public interface Listener {
        void onEnd(PagedList<TmpHum> data, int addSize);
    }
}
